package com.leyou.search.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.common.utils.BeanHelper;
import com.leyou.common.utils.JsonUtils;
import com.leyou.common.vo.PageResult;
import com.leyou.item.client.ItemClient;
import com.leyou.item.dto.*;
import com.leyou.search.dto.GoodsDTO;
import com.leyou.search.dto.SearchRequest;
import com.leyou.search.entity.Goods;
import com.leyou.search.repository.GoodsRepository;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.BucketOrder;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.SearchQuery;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class SearchService {

    @Autowired
    private ItemClient itemClient;

    @Autowired
    private GoodsRepository goodsRepository;

    /**
     * 创建Goods对象
     * @param spuDTO    一个SpuDTO对象
     * @return          Goods对象
     */
    public Goods buildGoods(SpuDTO spuDTO){
        // 0、spu的id
        Long spuId = spuDTO.getId();

        // 1、spu下所有的sku集合
        List<SkuDTO> skuDTOList = itemClient.querySkuListBySpuId(spuId);
        // 1.1 准备List封装结果：
        List<Map<String, Object>> skuList = new ArrayList<>();
        // 1.2 封装要的数据
        for (SkuDTO skuDTO : skuDTOList) {
            Map<String, Object> sku = new HashMap<>();
            sku.put("id", skuDTO.getId());
            sku.put("image", StringUtils.substringBefore(skuDTO.getImages(),","));// 截取第一个逗号之前的字符串
            sku.put("price", skuDTO.getPrice());
            sku.put("title", skuDTO.getTitle());
            skuList.add(sku);
        }

        // 2、商品的价格集合
        Set<Long> price = skuDTOList.stream().map(SkuDTO::getPrice).collect(Collectors.toSet());

        // 3、搜索字段，包含品牌名、分类名称、商品名称等
        // 3.1 分类的名称
        List<CategoryDTO> categoryDTOList = itemClient.queryCategoryByIds(spuDTO.getCategoryIds());
        String categoryName = categoryDTOList.stream().map(CategoryDTO::getName).collect(Collectors.joining());
        // 3.2 品牌名称
        BrandDTO brandDTO = itemClient.queryBrandById(spuDTO.getBrandId());
        String brandName = brandDTO.getName();
        // 3.3 用于搜索的
        String all = spuDTO.getName() + categoryName + brandName;

        // 4、规格参数集合
        Map<String, Object> specs = new HashMap<>();
        // 4.1 获取规格参数的key：规格参数名称作为key
        List<SpecParamDTO> specParamDTOS = itemClient.querySpecParam(null, spuDTO.getCid3(), true);
        // 4.2 查询出SpuDetail： 值来自于spuDetail中的通用规格参数和特有的规格参数
        SpuDetailDTO spuDetailDTO = itemClient.querySpuDetailBySpuId(spuId);
        // 4.2.1 获取通用规格参数
        Map<Long, Object> genericSpecMap = JsonUtils.toMap(spuDetailDTO.getGenericSpec(), Long.class, Object.class);
        // 4.2.2 获取特有的规格参数
        Map<Long, List<String>> specialSpecMap = JsonUtils.nativeRead(spuDetailDTO.getSpecialSpec(), new TypeReference<Map<Long, List<String>>>() {
        });

        // 4.3 把值存入specs中
        for (SpecParamDTO spd : specParamDTOS) {
            // 规格参数名
            String key = spd.getName();
            Object value ;
            if(spd.getGeneric()){
                // 通用属性
                value = genericSpecMap.get(spd.getId());
            }else{
                // 特有的属性
                value = specialSpecMap.get(spd.getId());
            }

            // 处理数字类型的规格参数
            if(spd.getNumeric()){
                // 如果是数字，我们算好区间
                value = chooseSegment(value, spd);
            }
            /*specs.put("屏幕尺寸", "4.0英寸-6.0英寸"); // 把value当成 字符串来匹配； 不需要 比对区间。页面就需要使用区间来算      ? < 5 <?
            specs.put("屏幕尺寸", "3.0英寸以下"); // 把value当成 字符串来匹配； 不需要 比对区间。页面就需要使用区间来算      ? < 5 <?
            specs.put("屏幕尺寸", "6.0英寸-7.0英寸"); // 把value当成 字符串来匹配； 不需要 比对区间。页面就需要使用区间来算      ? < 5 <?*/
            specs.put(key, value);
        }
        // 5、封装Goods对象
        Goods goods = new Goods();
        goods.setSkus(JsonUtils.toString(skuList));// spu下所有的sku集合
        goods.setAll(all); // 搜索字段，包含品牌名、分类名称、商品名称等
        goods.setPrice(price);// 商品的价格
        goods.setSpecs(specs);// 商品的规格参数
        goods.setId(spuId); // id
        goods.setSubTitle(spuDTO.getSubTitle()); // 副标题
        goods.setBrandId(spuDTO.getBrandId()); // 品牌的id
        goods.setCategoryId(spuDTO.getCid3());  // 分类id
        goods.setCreateTime(spuDTO.getCreateTime().getTime()); // 创建时间

        // 6、返回对象
        return goods;
    }

    // 处理数字片段：  知道就可以
    private String chooseSegment(Object value, SpecParamDTO p) {
        if (value == null || StringUtils.isBlank(value.toString())) {
            return "其它";
        }
        double val = parseDouble(value.toString());
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = parseDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    private double parseDouble(String str) {
        try {
            return Double.parseDouble(str);
        } catch (Exception e) {
            return 0;
        }
    }

    // spring提供的搜索模板，支持复杂的查询
    @Autowired
    private ElasticsearchTemplate esTemplate;
    /**
     * 搜索
     * @param sr
     * @return
     */
    public PageResult<GoodsDTO> search(SearchRequest sr) {
        // 0、判断关键字
        String key = sr.getKey();
        if(StringUtils.isBlank(key)){
            throw new LyException(ExceptionEnum.INVALID_PARAM_ERROR);
        }
        // 1、构建搜索对象
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 2、拼接条件
        /*  key = "周杰伦同款    or | and  手机"
        QueryBuilders.termsQuery()  :  词条搜索，不会分词
        QueryBuilders.matchQuery()  ： 会先把搜索内容分词，然后再去搜索
         */
        // 2.1 拼装搜索条件
        queryBuilder.withQuery(builderSearchKey(sr));

        // 2.2 分页条件
        queryBuilder.withPageable(PageRequest.of(sr.getPage()-1, sr.getSize()));

        // 2.3 指定获取的内容
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id","subTitle","skus"}, null));

        // 3、搜索结果
        AggregatedPage<Goods> aggregatedPage = esTemplate.queryForPage(queryBuilder.build(), Goods.class);

        // 4、解析结果
        // 4.1 总记录数
        long total = aggregatedPage.getTotalElements();
        // 4.2 总页数
        int totalPages = aggregatedPage.getTotalPages();
        // 4.3 当前页数据
        List<Goods> content = aggregatedPage.getContent();
        List<GoodsDTO> goodsDTOList = BeanHelper.copyWithCollection(content, GoodsDTO.class);

        // 5、封装数据并返回
        return new PageResult(goodsDTOList,  total, totalPages);
    }

    /**
     * 查出过滤项：
     * @param sr
     * @return
     */
    public Map<String, List<?>> filter(SearchRequest sr) {
        // 返回的map
        Map<String, List<?>> filterPramMap = new LinkedHashMap<>();

        // 0、判断关键字
        String key = sr.getKey();
        if(StringUtils.isBlank(key)){
            throw new LyException(ExceptionEnum.INVALID_PARAM_ERROR);
        }
        // 1、构建搜索对象
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 2、拼接条件
        // 2.1 拼装搜索条件
        queryBuilder.withQuery(builderSearchKey(sr));
        // 2.2 分页条件: 如果size指定0，SpringDataElasticSearch就不会去查询，所以写个1
        queryBuilder.withPageable(PageRequest.of(0, 1));
        // 2.3 指定获取的内容
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null));
        //==========================================================================================
        //=======================          开始添加聚合条件 ： 分类和品牌          ===============================
        //==========================================================================================
        // 2.4 添加聚合条件
        // 2.4.1 分类的聚合
        String categoryAgg = "categoryAgg";
        queryBuilder.addAggregation(AggregationBuilders.terms(categoryAgg).field("categoryId"));
        // 2.4.2 品牌的聚合
        String brandAgg = "brandAgg";
        queryBuilder.addAggregation(AggregationBuilders.terms(brandAgg).field("brandId"));
        // 3、搜索开始 ==================================================================================
        AggregatedPage<Goods> aggregatedPage = esTemplate.queryForPage(queryBuilder.build(), Goods.class);
        // 3、搜索结束 ==================================================================================

        // 3.1 获取到这个：Aggregations
        Aggregations aggregations = aggregatedPage.getAggregations();
        // 3.2 取出分类的聚合
        Terms categoryTerm = aggregations.get(categoryAgg);
        // 3.3 处理分类的聚合
        List<Long> categoryIds = handleCategoryFilter(categoryTerm, filterPramMap);
        // 3.4 取出品牌的聚合
        Terms brandTerm = aggregations.get(brandAgg);
        // 3.5 处理品牌的聚合
        handleBrandFilter(brandTerm, filterPramMap);

        //=================================================================================================
        // 4、处理规格参数：  再次聚合
        handleSpecParamFilter(categoryIds, sr, filterPramMap);  // 需要进行再一次的搜索的


        // 5、最后的返回
        return filterPramMap;
    }

    // 处理分类集合下所有的规格参数
    private void handleSpecParamFilter(List<Long> categoryIds, SearchRequest sr, Map<String, List<?>> filterPramMap) {
        for (Long cid : categoryIds) {
            // 查询出规格参数
            List<SpecParamDTO> specParamDTOList = itemClient.querySpecParam(null, cid, true);
            if(!CollectionUtils.isEmpty(specParamDTOList)){
                // 如果规格参数不为空，我们才去聚合
                // 1、构建搜索对象
                NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
                // 2、拼接条件
                // 2.1 拼装搜索条件
                queryBuilder.withQuery(builderSearchKey(sr));
                // 2.2 分页条件: 如果size指定0，SpringDataElasticSearch就不会去查询，所以写个1
                queryBuilder.withPageable(PageRequest.of(0, 1));
                // 2.3 指定获取的内容
                queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null));
                //==========================================================================================
                //=======================          开始添加聚合条件 : 规格参数          =====================
                //==========================================================================================
                for (SpecParamDTO sp : specParamDTOList) {
                    // 聚合名称
                    String aggName = sp.getName();
                    // field 的名称
                    String fieldName = "specs." + aggName;
                    queryBuilder.addAggregation(AggregationBuilders
                            .terms(aggName)  // 名字随便定义，取的时候，用这个取
                            .field(fieldName)// field 的名称
                            .size(20)        // 聚合之后取多少条
                            .order(BucketOrder.key(true)) // 排序
                    );
                }

                // 3、搜索开始 ==================================================================================
                AggregatedPage<Goods> aggregatedPage = esTemplate.queryForPage(queryBuilder.build(), Goods.class);
                // 3、搜索结束 ==================================================================================

                // 4、获取结果
                Aggregations aggregations = aggregatedPage.getAggregations();
                // 4.1 遍历所有的聚合结果
                for (SpecParamDTO spd : specParamDTOList) {
                    // 聚合名称
                    String aggName = spd.getName();
                    // 获取桶中的值
                    Terms terms = aggregations.get(aggName);
                    // 收集所有的过滤参数
                    List<String> specParm = terms.getBuckets().stream()
                            .map(Terms.Bucket::getKeyAsString)
                            .filter(StringUtils::isNotBlank)
                            .collect(Collectors.toList());
                    // 得到所有的参数之后，放入map中
                    filterPramMap.put(aggName, specParm);
                }
            }
        }
    }

    // 处理品牌的内容
    private void handleBrandFilter(Terms brandTerm, Map<String, List<?>> filterPramMap) {
        // 获取品牌的id
        List<Long> brandIds = brandTerm.getBuckets().stream().map(Terms.Bucket::getKeyAsNumber).map(Number::longValue).collect(Collectors.toList());
        //查询品牌的列表
        List<BrandDTO> brandDTOS = itemClient.queryBrandByIds(brandIds);
        //存入map中
        filterPramMap.put("品牌", brandDTOS);
    }

    // 处理分类的聚合内容
    private List<Long> handleCategoryFilter(Terms categoryTerm, Map<String, List<?>> filterPramMap) {
        List<? extends Terms.Bucket> buckets = categoryTerm.getBuckets();
        // 获取到所有的分类的id
        List<Long> cIds = new ArrayList<>();
        for (Terms.Bucket bucket : buckets) {
            // 取出key
            Long key = bucket.getKeyAsNumber().longValue();
            cIds.add(key);
        }
        // 找出分类的名称
        List<CategoryDTO> categoryDTOList = itemClient.queryCategoryByIds(cIds);
        // 存入map中
        filterPramMap.put("分类", categoryDTOList);
        // 返回分类的id集合，用于查询规格参数【可搜索】
        return cIds;
    }

    /**
     * 抽取过滤条件
     * @param sr
     * @return
     */
    private QueryBuilder builderSearchKey(SearchRequest sr){
         // 改造成 bool查询
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        // 1、拼接搜索关键字
        MatchQueryBuilder matchQuery = QueryBuilders.matchQuery("all", sr.getKey()).operator(Operator.AND);
        boolQuery.must(matchQuery);
        // 2、拼装过滤参数：filter是多个
        sr.getFilterMap().entrySet().forEach(entry -> {
            String key = entry.getKey();
            Object value = entry.getValue();
            String fieldName = "";
            if("品牌".equals(key)){
                fieldName = "brandId";
            }else if("分类".equals(key)){
                fieldName = "categoryId";
            }else{
                fieldName = "specs." + key;
            }
            boolQuery.filter(QueryBuilders.termsQuery(fieldName,value));
        });
        // 3、返回
        return boolQuery;
    }

    /**
     * 创建ES的索引
     * @param spuId
     */
    public void saveGoods(Long spuId) {
        // 获取spu数据
        SpuDTO spuDTO = itemClient.querySpuById(spuId);
        // 构建goods对象
        Goods goods = buildGoods(spuDTO);
        // 更新索引库
        goodsRepository.save(goods);
    }

    /**
     * 商品下架的时候删除索引库的数据
     * @param spuId
     */
    public void deleteGoods(Long spuId) {
        goodsRepository.deleteById(spuId);
    }
}
